package com.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.regex.Pattern;

/**
 * @author：zhangzhanheng
 * @createTime: 2021/11/5  11:30 上午
 * @desc: 校验身份证工具类
 * @version: 1.0.0
 **/
@Slf4j
public class CheckIdCardUtils {

    /** 身份证号的第0位 */
    public final static int          ZERO               = 0;

    /** 身份证号的第4位 */
    public final static int          FOUR               = 4;
    /** 身份证号的第6位 */
    public final static int          SIX                = 6;

    /** 身份证号的第8位 */
    public final static int          EIGHT              = 8;

    /** 身份证号的第10位 */
    public final static int          TEN                = 10;

    /** 身份证号的第12位 */
    public final static int          TWELVE             = 12;

    /** 身份证号的第14位 */
    public final static int          FOURTEEN           = 14;

    /** 身份证号的第15位 */
    public final static int          FIFTEEN            = 15;

    /** 身份证号的第16位 */
    public final static int          SIXTEEN            = 16;

    /** 身份证号的第17位 */
    public final static int          SEVENTEEN          = 17;

    /** 十九世纪 */
    public final static String       NINETEEN           = "19";

    /** 18位身份证最后的附加位 */
    public final static String       OVERHEAD_BIT       = "X";

    /** 十九世纪 */
    public final static int          THE_19_CENTURY     = 1900;

    /** 身份证号模式 */
    public static final String       ID_CARD_NUMBER_REG = "^((([0-9]){15})|(([0-9]){17}[0-9Xx]{1}))$";

    /** 生日,格式为19900303,8位生日格式*/
    private static final String      BIRTHDAY_REG       = "^(19|20)(\\d){2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[0-1])$";


    /**
     * 判断身份证是否有效:包括身份证长度、年龄
     * @param idCardNumber 身份证号码
     * @return true 身份证有效，false 身份证无效
     **/
    public static boolean checkIdCardNumberValid(String idCardNumber) {

        if (StringUtils.isBlank(idCardNumber)) {
            return false;
        }
        // 是否符合身份证正则表达式
        boolean matched = Pattern.matches(ID_CARD_NUMBER_REG, idCardNumber);
        if (!matched) {
            return false;
        }

        //校验生日是否有效
        if (!checkBirthDayValid(idCardNumber)) {
            return false;
        }

        //如果是18位身份证，校验最后一位
        if (idCardNumber.length() == 18) {
            String yearPrefix = idCardNumber.substring(6, 8);
            if (StringUtils.equals(yearPrefix, "19")) {
                // 注意下面的逻辑在非19XX年的身份证号的时候会有问题。
                if (!idCardNumber.equalsIgnoreCase(getIdCardNumber18(getIdCardNumber15(idCardNumber)))) {
                    return false;
                }
            } else {
                if (!isValidCertNO(idCardNumber)) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * 校验身份证号18位。
     *
     * @param idCardNumber 身份证号
     * @return 15位规格化的身份证号
     */
    public static boolean isValidCertNO(String idCardNumber) {

        if (StringUtils.isEmpty(idCardNumber)) {
            return false;
        }

        if (idCardNumber.length() != 18) {
            return false;
        }

        boolean matched = Pattern.matches(ID_CARD_NUMBER_REG, idCardNumber);
        if (!matched) {
            return false;
        }

        String idCardNumber17 = idCardNumber.substring(0, 17);
        String idCandNumber18 = idCardNumber.substring(17);
        //计算第18位
        int nSum = getOverHeadBit(idCardNumber17);

        //如果最后一位是10时实际身份证的最后一位为“X”
        if (nSum == 10) {
            return StringUtils.equalsIgnoreCase(idCandNumber18, OVERHEAD_BIT);
        } else {
            return StringUtils.equals(idCandNumber18, Integer.toString(nSum));
        }
    }

    /**
     * 将身份证号规格化为15位。
     *
     * @param idCardNumber 身份证号
     * @return 15位规格化的身份证号
     */
    public static String getIdCardNumber15(String idCardNumber) {
        if (!checkIdCardNumberValid(idCardNumber)) {
            return null;
        }

        if (idCardNumber.length() == 15) {
            return idCardNumber;
        }

        return idCardNumber.substring(ZERO, SIX) + idCardNumber.substring(EIGHT, SEVENTEEN);

    }

    /**
     * 将身份证号规格化为18位。
     *
     * @param idCardNumber  身份证号
     * @return 18位规格化的身份证号
     */
    public static String getIdCardNumber18(String idCardNumber) {

        //校验身份证号码的长度是否是15或者18位
        if (!checkIdCardNumberValid(idCardNumber)) {
            return null;
        }

        //如果传入的身份证已经是18位直接返回
        if (idCardNumber.length() == 18) {
            return idCardNumber.toUpperCase();
        }

        /* 首先将身份证号码扩展至17位: 将出生年扩展为19XX的形式 */
        String idCardNumber17 = idCardNumber.substring(ZERO, SIX) + NINETEEN
                + idCardNumber.substring(SIX);

        //计算第18位
        int nSum = getOverHeadBit(idCardNumber17);

        //如果最后一位是10时实际身份证的最后一位为“X”
        if (nSum == 10) {
            return idCardNumber17 + OVERHEAD_BIT;
        } else {
            return idCardNumber17 + nSum;
        }
    }

    /**
     * 校验生日格式是否合法<br/>
     * 格式为:19930303。8位格式
     *
     * @param birthday
     * @return true  合法
     *         false 非法
     */
    public static boolean checkBirthdayValid(String birthday) {
        if (StringUtils.isEmpty(birthday)) {
            return false;
        }

        if (birthday.length() != 8) {
            return false;
        }

        if (Pattern.matches(BIRTHDAY_REG, birthday)) {
            return true;
        }

        return false;
    }

    /**
     * 检查出生日期是否有效。
     *
     * @param idCardNumber 身份证号码
     * @return
     */
    public static boolean checkBirthDayValid(String idCardNumber) {
        String birthDay = getBirthday(idCardNumber);
        return isBirthdayValid(birthDay);
    }

    /**
     * 根据身份证号码提取会员的出生年月日(4位年\2位月份\2位日)
     * @param idCardNumber 身份证号码
     * @return 出生年月日
     **/
    public static String getBirthday(String idCardNumber) {
        if (!checkIdCardNumberValid(idCardNumber)) {
            return null;
        }

        String year = null;
        String month = null;
        String day = null;

        String idCardNo = idCardNumber.trim();

        //处理18位身份证
        if (idCardNo.length() == 18) {
            year = idCardNo.substring(SIX, TEN);
            month = idCardNo.substring(TEN, TWELVE);
            day = idCardNo.substring(TWELVE, FOURTEEN);

        }

        //处理15位身份证
        else {
            year = NINETEEN + idCardNo.substring(SIX, EIGHT);
            month = idCardNo.substring(EIGHT, TEN);
            day = idCardNo.substring(TEN, TWELVE);
        }

        if (month.length() == 1) {
            //补足两位
            month = String.valueOf(ZERO) + month;
        }
        if (day.length() == 1) {
            //补足两位
            day = String.valueOf(ZERO) + day;
        }
        return year + month + day;
    }


    /**
     * 判断生日是否有效。
     *
     * @param birthday
     * @return
     */
    private static boolean isBirthdayValid(String birthday) {
        try {
            if (StringUtils.isEmpty(birthday)) {
                return false;
            }

            //提取年月日
            int year = Integer.parseInt(birthday.substring(ZERO, FOUR));
            int month = Integer.parseInt(birthday.substring(FOUR, SIX));
            int day = Integer.parseInt(birthday.substring(SIX, EIGHT));

            //校验年份
            if ((year < THE_19_CENTURY) || (year > getCurrentYear())) {
                return false;
            }

            //校验月份
            if ((month < 1) || (month > 12)) {
                return false;
            }

            //校验日期
            Calendar cal = new GregorianCalendar();
            cal.set(year, month - 1, 1);
            if ((day < 1) || (day > cal.getActualMaximum(Calendar.DAY_OF_MONTH))) {
                return false;
            }

            return true;

        } catch (Exception ex) {
            log.warn(" 校验身份证号码中出生日期是否有效时发生异常 ", ex);
            return false;
        }
    }

    /**
     * 取得当前年份。
     *
     * @return
     */
    private static int getCurrentYear() {
        Calendar cal = new GregorianCalendar();

        cal.setTime(new Date());
        return cal.get(Calendar.YEAR);
    }

    /**
     * 根据前17位计算18位身份证号码的最后一位
     * @param idCardNumber17
     * @return
     */
    private static int getOverHeadBit(String idCardNumber17) {
        int nSum = 0;

        try {
            for (int nCount = 0; nCount < 17; nCount++) {
                nSum += (Integer.parseInt(idCardNumber17.substring(nCount, nCount + 1)) * (Math
                        .pow(2, 17 - nCount) % 11));
            }

            nSum %= 11;

            if (nSum <= 1) {
                nSum = 1 - nSum;
            } else {
                nSum = 12 - nSum;
            }
        } catch (Exception ex) {
            log.warn(" 15位身份证转化为18位身份证时，计算最后一位时发生异常 ", ex);
        }

        return nSum;
    }
}
